# -*- coding: utf-8 -*-
from matrix import Matrix

class linearSystem:
    """
    A linear equations systems solver using Cramer's method.
    by Guillaume HUYSMANS, October 2013 (LGPL license)
    
    Benchmarks (made with Matrix, a native/naïve implementation of matrices operations):
    * xxx
    * yyy
    """
    
    def __init__(self, sysMat, variables=None):
        """Variable names will be generated if not supplied."""
        if (sysMat.cols-1)!=sysMat.rows:
            raise ValueError("Invalid system matrix format!")
        if variables is None:
            ct = sysMat.rows
            if ct>26:
                variables = ['v'+str(v) for v in range(ct)]
            else:
                if ct<4:
                    s = 120 #x, y, z
                elif ct==4:
                    s = 119 #w, x, y, z
                else:
                    s = 97 #a, b, c...
                variables = [chr(s+v) for v in range(ct)]
        else:
            if sysMat.rows != len(variables):
                raise ValueError("There must be as much equations as variables!")
        self.matrix = sysMat
        self.vars = variables
        
    def __str__(self, numbered=False):
        """Returns a string representation of the system (ax+by=c...)"""
        s = ""
        lf = "{:0"+str(len(str(len(self.vars))))+"d}"
        for i, e in enumerate(self.matrix.arr):
            if numbered:
                s += "("+lf.format(i+1)+") "
            for j, v in enumerate(self.vars):
                s += str(self.matrix.arr[i][j])+self.vars[j]+" + "
            s = s[:-3] + " = " + str(self.matrix.arr[i][self.matrix.cols-1]) + "\n"
        return s[:-1]
    
    def solve(self):
        """Returns an array, or None if no exact solution is found."""
        coeff = self.matrix.subMatrix(self.matrix.rows, self.matrix.cols-1)
        det = coeff.det()
        if det==0:
            return
        sol = []
        consts = self.matrix.getCol(self.matrix.cols-1)
        for i,v in enumerate(self.vars):
            tmp = Matrix(coeff)
            tmp.setCol(i, consts)
            sol.append(float(tmp.det())/det)
        return sol

    def test(self, values):
        """
        Returns a matrix of constants computed from:
        * coefficients of the current system
        * the given solution (values), a list
        """
        coeffs_m = self.matrix.subMatrix(self.matrix.rows, self.matrix.cols-1)
        values_m = Matrix([values]).trans()
        consts_m = Matrix([self.matrix.getCol(self.matrix.cols-1)]).trans()
        return coeffs_m*values_m

    def random(var_ct):
        """Returns a random linear equations system with var_ct variables"""
        values = Matrix.random(1, var_ct, -100, 100).arr[0] #a bit crappy but it works
        sysMat = Matrix.random(var_ct, var_ct+1, -100, 100)
        consts_m = linearSystem(sysMat).test(values)
        sysMat.setCol(var_ct, consts_m)
        return linearSystem(Matrix(sysMat))
    random = staticmethod(random)



#Testing stub
if __name__ == "__main__":
    def test_hc():
        print "Hardcoded system"
        mySys = linearSystem(Matrix([[2,3,41],[3,5,66]]))
        print mySys
        print "sol:", mySys.solve()

    def test_rs(n):
        print "Random system"
        ranSys = linearSystem.random(n)
        print ranSys
        ranSol = ranSys.solve()
        print "sol:", ranSol
        print "tst:", ranSys.test(ranSol).arr

    def test_dp():
        print "Dot product (vectors)"
        v1 = Matrix([[1,2]])
        v2 = Matrix([[2,-1]])
        print "v1:", v1.arr
        print "v2:", v2.arr
        print "dp:", (v1*v2.trans()).arr

    test_hc()
    test_dp()
    for i in range(50):
        pass #test_rs(5)
